package vcp.server.requesthandler;

import java.io.Serializable;
import java.util.ArrayList;

import ocsf.server.ConnectionToClient;

import org.joda.time.DateTime;

import vcp.client.request.CustomerCheckInRequest;
import vcp.client.request.response.ResponseRequest;
import vcp.client.utility.Common.UserNotification;
import vcp.entity.Car;
import vcp.entity.Lot;
import vcp.entity.Site;
import vcp.entity.Subscription;
import vcp.entity.User;
import vcp.entity.utility.Robot;
import vcp.entity.utility.RobotManager;
import vcp.enums.LotStatus;
import vcp.enums.SubscriptionType;
import vcp.server.DAO.CarDAO;
import vcp.server.DAO.LotDAO;
import vcp.server.DAO.SiteDAO;
import vcp.server.DAO.SubscriptionDAO;
import vcp.server.DAO.UserDAO;
import vcp.server.interfaces.IServer;
import vcp.server.utility.Constants.TableNames;
import vcp.server.utility.SystemClock;
/**
 * 
 * @author VCP team
 *<p>
 *this class handles the request to check in customer on server side
 *</p>
 */
public class CustomerCheckInRequestHandler extends RequestHandler <CustomerCheckInRequest>
{
	/**
	 * class constructor 
	 */
	public CustomerCheckInRequestHandler ( IServer server )
	{
		super(server);
	}

	/****************************************************************************************************************************
	 *** CommandHandler methods ***
	 ****************************************************************************************************************************/
	@Override
	public void execute ( CustomerCheckInRequest request, ConnectionToClient client )
	{
		ResponseRequest response = new ResponseRequest(request.getRequestID(), null);
		SiteDAO siteDAO = new SiteDAO();
		Site requiredSite = siteDAO.findByID(request.getSite().getSiteID());

		if ( this.hasFreeLots(requiredSite) )
		{
			CarDAO carDAO = new CarDAO();
			Car car = carDAO.findByID(request.getCarID());

			ArrayList <Serializable> serializableList = new ArrayList <>();
			Subscription subscription = this.fillSubscriptionWithData(request, carDAO);
			if ( car == null )
			{
				UserDAO userDAO = new UserDAO();
				User findUser = userDAO.findByID(request.getUserID());

				if ( findUser != null )
				{
					String extraData = String.format(UserNotification.CAR__ID__DOESNT_MATCH_YOUR_SUBSCRIPTION, request.getCarID());
					serializableList.add(extraData);
				}

				serializableList.add(subscription);
				response.setData(serializableList);
			}
			else if ( car.isParkedOnSite() )
			{
				String extraData = String.format(UserNotification.THIS_CAR_ALREADY_PARKED, car.getCarID());
				response.setErrorMessage(extraData);
			}
			else
			{
				SubscriptionDAO subscriptionDAO = new SubscriptionDAO();
				Subscription existSubscription = subscriptionDAO.findByID(car.getSubscriptionID());

				if ( this.hasSubscription(request, existSubscription) )
				{
					if ( this.isFullSubscriber(existSubscription) )
					{
						this.parkCar(request, client, car);
						response.setData(new Boolean(true));
					}
					else
					{
						DateTime todayDate = SystemClock.getClockInstance().getSystemDateTime();

						// not full subscriber
						if ( existSubscription.getSubscriptionType() == SubscriptionType.CASUAL_SUBSCRIBER )
						{
							if ( this.isDateNotAvailableForCasualSubscriber(car, todayDate) )
							{
								serializableList.add(UserNotification.TODAYS_PARKING_NOT_ALLOWED_ACCORDING_TO_YOUR_SUB);
								serializableList.add(subscription);
								response.setData(serializableList);
							}
							else
							{
								this.parkCar(request, client, car);
								response.setData(new Boolean(true));
							}
						}
						else
						{
							if ( this.isDateNotAvailableForOrderAheadSubscriber(existSubscription) )
							{
								serializableList.add(UserNotification.YOU_CANNOT_ENTER_IN_BEFORE_AFTER_REGISTERED_ENTER_TIME);
								serializableList.add(subscription);
								response.setData(serializableList);
							}
							else
							{
								this.parkCar(request, client, car);
								response.setData(new Boolean(true));
							}
						}
					}
				}
				else
				{
					String extraData = String.format(
							UserNotification.__USER_SUBSCRIBED_TO_SITE__SITE_A_THEREFOR_CANNOT_CHECK_IN_INTO__SITE_B__
							, request.getUserID()
							, request.getSite().toString()
							, existSubscription.getSite().toString());

					serializableList.add(extraData);
					serializableList.add(subscription);

					response.setData(serializableList);
				}
			}
		}
		else
		{
			Site nonFullSite = siteDAO.findSiteWithFreeLots();
			String notification = nonFullSite != null
					? String.format(UserNotification.SITE__SiteName__APPEARS_TO_BE_FULL_TRY__SiteNameB, requiredSite.getSiteName(), nonFullSite.getSiteName(), nonFullSite.getFreeLots())
							: UserNotification.UNFORTUNATELY_ALL_SITE_FULL;

					response.setData(notification);
		}
		this.sendResponse(response, client);
	}
	/**
	 * checks available lots on requested site
	 * @param requiredSite to be checked
	 * @return true if there is available lot
	 */
	private boolean hasFreeLots ( Site requiredSite )
	{
		return requiredSite.getFreeLots() > 0;
	}
	/**
	 *  updates the database that robot will park a car
	 * @param request from check in 
	 * @param client that sent the request
	 * @param car to be parked
	 */
	private void parkCar ( CustomerCheckInRequest request, ConnectionToClient client, Car car )
	{
		SiteDAO siteDAO = new SiteDAO();
		LotDAO lotDAO = new LotDAO();
		SubscriptionDAO subscriptionDAO = new SubscriptionDAO();
		Subscription subscription = subscriptionDAO.findByID(car.getSubscriptionID());
		Lot lot = lotDAO.findByReservedSubscriptionID(car.getSubscriptionID());

		this.deleteReservation(request, lotDAO, lot);

		Robot robot = RobotManager.getRobotManagerInstance().getSiteRobot(request.getSite());

		lot = robot.getAvailableLot(subscription);

		lot.setLotStatus(LotStatus.OCCUPIED);
		lot.setCarID(car.getCarID());

		lotDAO.updateLotStatusByID(request.getSite().getSiteID(), lot.getFloor(), lot.getRow(), lot.getColumn(), lot.getLotStatus());
		lotDAO.updateCarInLot(request.getSite().getSiteID(), lot.getFloor(), lot.getRow(), lot.getColumn(), lot.getCarID());

		CarDAO carDAO = new CarDAO();
		carDAO.updateArrivalTime(car.getCarID(), SystemClock.getClockInstance().getSystemDateTime());
		carDAO.updateParkedOnSite(car.getCarID(), true);

		Site site = siteDAO.findByID(request.getSite().getSiteID());
		siteDAO.getSiteLotsOnDemand(site);

		site.setOccupiedLots(site.getOccupiedLots() + 1);
		siteDAO.updateOccupiedLotsByID(site.getSiteID(), site.getOccupiedLots());

		this.sendResponseRefresh(site, client);
	}
	/**
	 * deletes the reservation before actual parking 
	 * @param request
	 * @param lotDAO
	 * @param lot that was reserved
	 */
	private void deleteReservation(CustomerCheckInRequest request, LotDAO lotDAO, Lot lot)
	{
		if(lot != null)
		{
			if (lot.getParentSite().getSite() == request.getSite())
			{
				lot.setReservedBySubscription(null);
				lot.setLotStatus(LotStatus.FREE);
				lot.setCarID(null);
				lotDAO.updateLotStatusByID(request.getSite().getSiteID(), lot.getFloor(), lot.getRow(), lot.getColumn(), lot.getLotStatus());
				lotDAO.updateCarInLot(request.getSite().getSiteID(), lot.getFloor(), lot.getRow(), lot.getColumn(), lot.getCarID());
				lotDAO.updateReservedBySubscription(request.getSite().getSiteID(), lot.getFloor(), lot.getRow(), lot.getColumn(), lot.getReservedBySubscription());
			}
		}
	}
	/**
	 * restricts order ahead subscriber to park only the date that was mentioned in the form
	 * @param existSubscription 
	 * @return true is the subscriber arrived in the written date in form
	 */
	private boolean isDateNotAvailableForOrderAheadSubscriber ( Subscription existSubscription )
	{
		return SystemClock.getClockInstance().isDateBeforeToday(existSubscription.getArrivalDate())
				|| SystemClock.getClockInstance().isDateAfterToday(existSubscription.getEstimatedLeaveDate());
	}
	/**
	 * restricts casual subscriber to park only the date that was mentioned in the form and doesn't allow parking on weekends
	 * @param existSubscription 
	 * @return true is the subscriber arrived in the written date in form
	 */
	private boolean isDateNotAvailableForCasualSubscriber ( Car car, DateTime todayDate )
	{
		return ! car.getLastCheckOutDate().isBefore(todayDate) || todayDate.getDayOfWeek() == 5 || todayDate.getDayOfWeek() == 6;
	}
	/**
	 * checks if existing subscriber is full subscriber
	 * @param existSubscription
	 * @return true if full subscriber
	 */
	private boolean isFullSubscriber ( Subscription existSubscription )
	{
		return existSubscription.getSite() == null;
	}
	/**
	 *  checks if customer has any kind of subscription
	 * @param request
	 * @param existSubscription
	 * @return true if customer is subscriber
	 */
	private boolean hasSubscription ( CustomerCheckInRequest request, Subscription existSubscription )
	{
		return this.isFullSubscriber(existSubscription) || this.isSubscribedToSite(request, existSubscription);
	}
	/**
	 * checks if subscriber was attempting to check in to other site that the registered (except for full)
	 * @param request
	 * @param existSubscription
	 * @return true if customer checks in to registered site
	 */
	private boolean isSubscribedToSite ( CustomerCheckInRequest request, Subscription existSubscription )
	{
		return existSubscription.getSite().getSiteID() == request.getSite().getSiteID();
	}
	/**
	 * option for regular check in for unregistered customer or customer registered to another site
	 * @param request
	 * @param carDAO
	 * @return new subscription
	 */
	private Subscription fillSubscriptionWithData ( CustomerCheckInRequest request, CarDAO carDAO )
	{
		Subscription newSubscription = new Subscription();

		newSubscription.setSubscribtionID(this.server.getGeneratedID(TableNames.SUBSCRIPTION));
		newSubscription.setCustomerID(request.getUserID());
		newSubscription.setSite(request.getSite());
		newSubscription.setSubscriptionType(SubscriptionType.ONE_TIME_USE);
		newSubscription.setStartDate(SystemClock.getClockInstance().getSystemDateTime());
		newSubscription.setArrivalDate(SystemClock.getClockInstance().getSystemDateTime());

		ArrayList <Car> cars = new ArrayList <>();
		Car car = carDAO.create();
		car.setCarID(request.getCarID());
		car.setCustomerID(request.getUserID());
		car.setSubscriptionID(newSubscription.getSubscribtionID());
		cars.add(car);

		newSubscription.setCars(cars);

		return newSubscription;
	}
}
